#ifndef __CHUNK_H__
#define __CHUNK_H__

#include "yid.h"
#include "cluster.h"
#include "configure.h"

/**
 * @file Chunk/Replica Status
 *
 * 关键问题是如何维护副本一致性，并做出相应的校正策略？
 *
 * 可以分为两级去理解：chunk级和replica级。
 *
 * chkstat.chkstat_clock维护io->clock，用于确保一个chunk的副本一致性。
 *
 * 在每个副本上的所有io操作，维护clock和dirty两变量，用于标记一个操作的事务。
 * 事务开始dirty=1，事务提交dirty=0。
 *
 * table2->chunk_check过程，如果必要的话(needcheck条件)，会检查副本一致性
 *
 * @todo 不一致时的处理策略：
 * - 有online副本，选择一个clean副本，覆盖其它副本
 * - 无online副本，fully_check
 *
 * chkinfo.diskid[i].status不同于replica级的dirty变量，
 * 当缺少副本而又申请不到空间时，会把chunk设为dirty。
 *
 * chkinfo和chkstat，联合起来表示一个chunk的内存结构。
 * chkinfo是持久化元数据，chkstat则是动态构造而来。
 * 基于这些信息，可以判断出一个chunk的多副本之间的一致性。
 */

#pragma pack(8)

typedef struct {
        chkid_t  id;            // ID of object
        uint16_t magic;
        uint16_t repnum;        // Count of replication
        uint32_t mtime;         // last move time*/
        uint64_t info_version;  // Increase when update
        uint64_t snap_version;  // 与fileinfo的snap_version配合使用, 快照/卷
        reploc_t diskid[0];     // Replications
} chkinfo_t;

#pragma pack()

#if 0
typedef struct {
        chkinfo_t *chkinfo;
        char __buf[CHKINFO_MAX];
} chkinfo_max_t;

static inline void chkinfo_max_init(chkinfo_max_t *chkinfo) {
        chkinfo->chkinfo = (void *)chkinfo->__buf;
}
#endif

typedef struct {
        time_t ltime;
        uint32_t magic;
} repstat_t;

typedef struct {
        uint64_t chkstat_clock;
        uint32_t magic;
        uint32_t read;
        uint32_t write;
        repstat_t repstat[0];
} chkstat_t;

typedef enum {
        __OP_NOOP,
        __OP_WRITE,
        __OP_READ,
        __OP_SYNCED,
} replica_io_op_t;

#define CHKSTAT_SIZE(__repnum__) (sizeof(chkstat_t) + sizeof(repstat_t) * __repnum__)
#define CHKSTAT_MAX (CHKSTAT_SIZE(LICH_REPLICA_MAX))
#define CHKINFO_CP(__to__, __from__) (memcpy(__to__, __from__, CHKINFO_SIZE(__from__->repnum)))
#define CHKSTAT_CP(__to__, __from__, __count__) (memcpy(__to__, __from__, CHKSTAT_SIZE(__count__)))

#define CHKINFO_FIRSTNID(chkinfo) (&chkinfo->diskid[0].id)


#define SHA1_SIZE (40 + 1)

typedef struct {
        uint32_t count;
        char     md[0][SHA1_SIZE];
} sha1_result_t;

#define CHKINFO_SIZE(__repnum__) (sizeof(chkinfo_t) + sizeof(reploc_t) * __repnum__)

#define CHKINFO_MAX (CHKINFO_SIZE(LICH_REPLICA_MAX))

#define CHKINFO_STR(__chkinfo__, __buf__)                             \
        do {                                                            \
                int i;                                                  \
                char *chkinfo_str_buf;                                  \
                const char *stat;                                       \
                const reploc_t *diskid;                                 \
                chkinfo_str_buf = __malloc(MAX_BUF_LEN);                \
                chkinfo_str_buf[0] = '\0';                                          \
                for (i = 0; i < (int)__chkinfo__->repnum; ++i) {        \
                        diskid = &__chkinfo__->diskid[i];               \
                        if (ng.daemon) {                                \
                                network_connect(&diskid->id, NULL, 0, 0); \
                        } else {                                        \
                                network_connect(&diskid->id, NULL, 1, 0); \
                        }                                               \
                        if (diskid->status == __S_DIRTY) {              \
                                stat = "dirty";                         \
                        } else if (diskid->status == __S_CHECK) {        \
                                stat = "check";                         \
                        } else if (netable_connected(&diskid->id) == 0) { \
                                stat = "offline";                       \
                        } else {                                        \
                                stat = "clean";                         \
                        }                                               \
                        snprintf(chkinfo_str_buf + strlen(chkinfo_str_buf), MAX_NAME_LEN, "%s:%s ", \
                                 network_rname(&diskid->id), stat);      \
                }                                                       \
                snprintf(__buf__, MAX_BUF_LEN, "chunk %s info_version %llu @ [%s]", \
                         id2str(&__chkinfo__->id), (LLU)__chkinfo__->info_version, chkinfo_str_buf); \
                __free(chkinfo_str_buf);                                \
        } while (0);


#define CHKINFO_DUMP(__chkinfo__, __mask__)              \
        do {                                            \
                char *__buf1__;                                         \
                __buf1__ = __malloc(MAX_BUF_LEN);                       \
                CHKINFO_STR(__chkinfo__, __buf1__);             \
                D_MSG(__mask__, "INFO: %s\n", __buf1__);        \
                __free(__buf1__);                                       \
        } while (0);

#define CHKINFO_DUMP1(__chkinfo__, __mask__, __parent__, __loc__)       \
        do {                                            \
                char *__buf1__;                                         \
                __buf1__ = __malloc(MAX_BUF_LEN);                       \
                CHKINFO_STR(__chkinfo__, __buf1__);                     \
                D_MSG(__mask__, "INFO: %s parent %s @ %s\n", \
                      __buf1__, CHKID_ARG(__parent__), __loc__);        \
                __free(__buf1__);                                       \
        } while (0);


#define CHKINFO_SORT(__chkinfo__, __master__)                           \
        if ((__master__) != 0) {                                        \
                int __i__;                                              \
                nid_t __disks__[LICH_REPLICA_MAX];                          \
                memcpy(__disks__, (__chkinfo__)->diskid, sizeof((__chkinfo__)->diskid[0]) * (__chkinfo__)->repnum); \
                for (__i__ = 0; __i__ < (__chkinfo__)->repnum; __i__++) {     \
                        (__chkinfo__)->diskid[__i__] = __disks__[(__i__ + (__master__)) % (__chkinfo__)->repnum]; \
                }                                                       \
        }

#endif
